/**
 * @author xavier martin, kzm
 * @version 1.0
 * @license MIT-License (2007)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

 */
package com.mediacatalyst.math.geom {
	import flash.geom.Matrix;
	import flash.geom.Point;
	
	import com.mediacatalyst.lang.ICompareable;
	import com.mediacatalyst.lang.IDeconstructable;
	import com.mediacatalyst.lang.INaturalObject;
	import com.mediacatalyst.lang.deconstructObject;
	import com.mediacatalyst.patterns.prototype.IPrototype;	
/**
 * Class for doing vector math.
 */
	public class Vector implements INaturalObject, IDeconstructable{
		private var _x:Number;
		private var _y:Number;
		private var _origin:Point;
		/**
		 * @param x The x magnitude.
		 * @param y The y magnitude.
		 * @param Origin the origin of the vector.
		 */
		public function Vector(x:Number,y:Number,origin:Point=null){
			_x = x;
			_y = y;
			this.origin = new Point(origin.x,origin.y);
		}
		/**
		 * @see com.mediacatalyst.lang.IDeconstructable#deconstruct()
		 */
		public function deconstruct():void{
			deconstructObject(_origin);
		}

		public function get origin():Point{
			return _origin;
		}

		public function set origin(point:Point):void{
			_origin = point==null?new Point(0,0):new Point(point.x,point.y);;
		}
		/**
		 * X magnitude.
		 */
		public function get x():Number{
			return _x;
		}
		/**
		 * Y magnitude.
		 */
		public function get y():Number{
			return _y;
		}
		/**
		 * Y magnitude.
		 */
		public function set y(val:Number):void{
			_y = val;
		}
		/**
		 * X magnitude.
		 */
		public function set x(val:Number):void{
			_x = val;
		}
		/**
		 * @return <code>true</code> if no magnitude
		 */
		public function get zero():Boolean{
			if ( x == 0 && y == 0 ) 
				return true;
			else 
				return false;
		}
		
		public function get length():Number{
			return Math.sqrt(lengthSquared);
		}

		public function get lengthSquared():Number{
			return Math.pow(x,2) +  Math.pow(y,2);
		}

		public function normalize():void{
			var l:Number = length;
			if(l!=0){
				x /= l;
				y /= l;
			}
		}

		public function reverse():void{
			x *= -1;
			y *= -1;
		}
		/**
		 * Truncate the vector to a given length.
		 */
		public function truncate(leng:Number):void{
			if( length > leng ){
				normalize();
				x *= leng;
				y *= leng;
			}
		}
		/**
		 * The squared distance to another vector.
		 */
		public function distanceSquared(vector:Vector):Number{
			var yDist:Number = vector.y - y;
			var xDist:Number = vector.x - x;
			return Math.pow(yDist,2) + Math.pow(xDist,2);
		}
		/**
		 * The distance to another vector.
		 */
		public function distance(vector:Vector):Number{
			return Math.sqrt(distanceSquared(vector));
		}
		/**
		 * Returns the dot value of both vectors.
		 */
		public function dot(vector:Vector):Number{
			return (x*vector.x + y*vector.y);
		}

		public function scale(num:Number):void{
			x *= num;
			y *= num;
		}
		/**
		 * Copy from a vector.
		 */
		public function copy(vector:Vector):void{
			x = vector.x;
			y = vector.y;
		}
		/**
		 * @see com.mediacatalyst.patterns.prototype.IPrototype#clone()
		 */
		public function clone():IPrototype{
			return new Vector(x,y);
		}

		public function equals(vector:ICompareable):Boolean{
			return (vector is Vector) && Vector(vector).x==x && Vector(vector).y==y;
		}
		/**
		 * Gets the angle to another vector in radians (default) or degrees.
		 */
		public function angle(vector:Vector,inDegrees:Boolean=false):Number{
			var cosA:Number;
			if (zero && !vector.zero) 
				cosA = vector.x;
			else if (!zero && vector.zero) 
				cosA = x;
			else 
				cosA = dot(vector)/(length*vector.length);
			var a:Number = Math.acos( cosA );
			if (inDegrees)
				return a / Math.PI * 180;
			return a;
		}
		public function rotate(ang:Number):void{
			var mtx:Matrix = new Matrix();
			mtx.rotate(ang);
			var oldp:Point = new Point(x,y);
			var newp:Point = mtx.transformPoint(oldp);
			x = newp.x;
			y = newp.y;
		}
		/** 
		  * Sets the vector by a given angle in radians.
		  */
		public function byAngle(ang:Number,length:Number=1):void{
			x = Math.cos(ang)*length;
			y = Math.sin(ang)*length;
		}
		/**
		 * Returns the vector as angle in radian, use *180/Math.PI to convert to degrees.
		 */
		public function asAngle():Number{
			return Math.atan2(y,x);
		}
		
		public function perpendicular(vector:Vector):Boolean{
			return 0==dot(vector);
		}

		public function colinear(vector:Vector):Boolean{
			vector.normalize();
			var cv:Vector = Vector(clone());
			var uv:Vector = Vector(clone());
			cv.reverse();
			uv.normalize();
			return (uv == vector || uv == cv);
		}
		/**
		 * @param rightHand give right hand normal, otherwise lefthand.
		 */
		public function normal(rightHand:Boolean):Vector{
			if(rightHand)
				return new Vector(x,-y);
			else
				return new Vector(-x,y);
		}
		/**
		 * Projects the vector on a given vector.
		 * Can be useful to translate vector to another coordinate system.
		 */
		public function project(vector:Vector):void{
			var dp:Number = dot(vector);
			x = dp*vector.x;
			y = dp*vector.y;
		}
		/**
		 * Like a dot product but using the normal.
		 * @see #dot()
		 */
		public function perp(vector:Vector):Number{
       		return x*vector.y-y*vector.x;
		}
		/**
		 * The intersection point if any (null).
		 */
		public function intersects(vector:Vector):Point{
			var vn:Vector = Vector(vector.clone());
			vn.normalize();
			var n:Vector = Vector(clone());
			n.normalize();
			if(equals(vector) || 
			  (vn.x==n.x && vn.y==n.y || vn.x==-n.x && vn.y==-n.y))
				return null;
			var v:Vector = new Vector(vector.origin.x-origin.x,vector.origin.y-origin.y);
			var t:Number = v.perp(vector)/perp(vector);
       		return new Point(origin.x+x*t,origin.y+y*t);
		}
		/**
		 * Reflects the vector through another.. like bouncing.
		 */
		public function reflect(vector:Vector):void{
       		/*
       		var p1:Vector = clone();
       		p1.project(vector);
       		var p2:Vector = clone();
       		p2.project((vector.clone().normalize()));
       		p2.reverse();
       		x = p1.x+p2.x;
       		y = p1.y+p2.y;
       		*/
       		y += 2*dot(vector)*Vector(vector.reverse()).y;
			x += 2*dot(vector)*Vector(vector.reverse()).x;
		}
		/**
		 * @see com.mediacatalyst.lang.IPrintable#toString()
		 */
		public function toString():String{
			return "Vector ("+x+","+y+")";
		}
	}	
}